unit MerWFMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, System.Actions,
  Vcl.ActnList, Vcl.ImgList, Vcl.ToolWin, Vcl.Menus, VCLTee.TeEngine,
  Vcl.ExtCtrls, VCLTee.TeeProcs, VCLTee.Chart, LJUDDelphi, Vcl.StdCtrls,
  VCLTee.Series, Vcl.Samples.Gauges, Vcl.DBCtrls, Data.DB, Datasnap.DBClient,
  Vcl.Buttons, Vcl.StdActns,w1025G_Iface, Vcl.ExtDlgs, LJConstants, ksTimers, Math;

(***CONSTANTS***)
const
  TORROFFSET = 10;    //offset value to add to TORR slider- equivalent to minimum desired TORR value.

(*****************************************************************************
 TCardiacPattern: Enumerated type for cardiac pattern
******************************************************************************)
type TCardiacPattern = (cpSquareWave, cpCardiacTyp, cpTriangle);

(*****************************************************************************
 TfrmMain: Main Form Class
******************************************************************************)

type
  TfrmMain = class(TForm)
    sbStatus: TStatusBar;
    mmMenu: TMainMenu;
    File1: TMenuItem;
    Exit1: TMenuItem;
    tbMain: TToolBar;
    alAction: TActionList;
    LJConnection: TAction;
    ToolButton1: TToolButton;
    LJConfigStream: TAction;
    tmrStream: TTimer;
    tcGraph1: TChart;
    VSens0: TFastLineSeries;
    VSens1: TFastLineSeries;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label5: TLabel;
    gaU6Backlog: TGauge;
    gaDrvBacklog: TGauge;
    Label4: TLabel;
    Label7: TLabel;
    Label11: TLabel;
    rgAmpGain: TRadioGroup;
    cbResolutionCh0: TComboBox;
    cbGainCh0: TComboBox;
    cbCh0: TCheckBox;
    cbCh1: TCheckBox;
    View: TMenuItem;
    TabSheet2: TTabSheet;
    Bevel1: TBevel;
    lblXpoints: TLabel;
    btnClearGraph: TButton;
    Bevel3: TBevel;
    FileExit1: TFileExit;
    TabSheet3: TTabSheet;
    sbDacA: TScrollBar;
    sbDacB: TScrollBar;
    Label9: TLabel;
    Label10: TLabel;
    lblDacA: TLabel;
    lblDacB: TLabel;
    btnCheckCal: TButton;
    pnlVDacA: TPanel;
    pnlVDacB: TPanel;
    pnlWaveForm: TPanel;
    btnSelectWaveForm: TButton;
    imgWaveForm: TImage;
    lblWaveForm: TLabel;
    btnRun: TButton;
    btnStop: TButton;
    sbRate: TScrollBar;
    sbOutput: TScrollBar;
    Label12: TLabel;
    lblVOut: TLabel;
    lblBPS: TLabel;
    Label13: TLabel;
    Label15: TLabel;
    pnlScan: TPanel;
    bbRewind: TBitBtn;
    Label6: TLabel;
    bbLast: TBitBtn;
    bbFirst: TBitBtn;
    bbForward: TBitBtn;
    lblTotalScans: TLabel;
    lblScanNum: TLabel;
    btnStartScan: TButton;
    btnStopScan: TButton;
    Bevel2: TBevel;
    pbScan: TProgressBar;
    Label16: TLabel;
    pnlFreeRun: TPanel;
    Label17: TLabel;
    btnAinScrollStart: TButton;
    btnAinScrollStop: TButton;
    tbFreeRun: TTrackBar;
    lblFreeRunPeriod: TLabel;
    Label18: TLabel;
    sbTiming: TScrollBar;
    Label8: TLabel;
    cbScanRate: TComboBox;
    sbPtsPerPage: TScrollBar;
    Label19: TLabel;
    lblPtsPerPage: TLabel;
    OpenDialog1: TOpenDialog;
    Button2: TButton;
    tmrScroll: TksTimer;
    pnlTests: TPanel;
    btnTubingFr: TButton;
    Label21: TLabel;
    pnlPPGActive: TPanel;
    lblScanActive: TPanel;
    edExcitation: TEdit;
    Label14: TLabel;
    ShowExcitationEdit: TMenuItem;
    ShowPredefinedTests: TMenuItem;
    SaveCurrentGraph: TMenuItem;
    SaveDialog1: TSaveDialog;
    procedure LJConnectionExecute(Sender: TObject);
    procedure btnClearGraphClick(Sender: TObject);
    procedure rgAmpGainClick(Sender: TObject);
    procedure btnAinScrollStartClick(Sender: TObject);
    procedure GetInitialValues;
    procedure cbGainCh0Click(Sender: TObject);
    procedure tmrScrollTimer(Sender: TObject);
    procedure btnAinScrollStopClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure PrepareFastChart;
    procedure btnStartScanClick(Sender: TObject);
    procedure btnStopScanClick(Sender: TObject);
    procedure tmrStreamTimer(Sender: TObject);
    procedure cbScanRateCloseUp(Sender: TObject);
    procedure cbResolutionCh0CloseUp(Sender: TObject);
    procedure cbCh0Click(Sender: TObject);
    procedure cbCh1Click(Sender: TObject);
    procedure InitParams;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ReviewScan(Sender: TObject);
    procedure UpdateScanGraphActive;
    procedure UpdateScanGraphFromList(val: Integer);
    procedure PageControl1Change(Sender: TObject);
    procedure tcGraph1AfterDraw(Sender: TObject);
    procedure sbDacAChange(Sender: TObject);
    procedure sbDacBChange(Sender: TObject);
    procedure btnCheckCalClick(Sender: TObject);
    procedure sbOutputChange(Sender: TObject);
    procedure sbRateChange(Sender: TObject);
    procedure tbFreeRunChange(Sender: TObject);
    procedure sbPtsPerPageChange(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure LoadWaveForm(fN: string);
    procedure btnRunClick(Sender: TObject);
    procedure btnFRClick(Sender: TObject);
    procedure tcGraph1Zoom(Sender: TObject);
    procedure btnSelectWaveFormClick(Sender: TObject);
    procedure DrawCrossHairs(color: TColor; pt: TPoint);
    procedure DrawAmpLines(color: TColor);
    procedure pnlFreeRunClick(Sender: TObject);
    procedure edExcitationChange(Sender: TObject);
    procedure ShowExcitationEditClick(Sender: TObject);
    procedure ShowPredefinedTestsClick(Sender: TObject);
    procedure File1Click(Sender: TObject);
    procedure SaveCurrentGraphClick(Sender: TObject);


  private
    { Private declarations }
  public
    { Public declarations }
    xPos: Integer;
    ScanArray0: array of array of Double;   //multidimnsional dynamic array for scan page 0
    ScanArray1: array of array of Double;  //multidimnsional dynamic array for scan page 1
    ScanIndex: Integer; //ScanIndex keeps track of the current scan page that is being displayed.
    CalMode: Boolean;
    CalXA, CalXALast, CalXB, CalY: Integer;
    WaveOutLast: Double;
    CurrentWaveFrm: TCardiacPattern;
    BeatList: TStringList;
    CurrentLine: Integer;
    cpRampDir: Integer;   //0 = ramping up, 1 = ramping down
    xPtLower: Integer;    //lower val of currently displayed xpoints on graph
    xPtUpper: Integer;    //upper val of currently displayed xpoints on graph
    sPt1, sPt2, sPt3: TPoint;   //holds screen pixle locations corresponing to x/y pt locations
    PPGActive: Boolean;    //flag to indicate if PPG is currently running astable.
  end;

var
  frmMain: TfrmMain; LJ:TLabJack; HT: THT1025;

implementation

{$R *.dfm}


(***************************************************************************
btnClearGraph: Clears VSens series and the graph.
*******************************************************************************)
procedure TfrmMain.btnClearGraphClick(Sender: TObject);
var
  i: Integer;
begin
  VSens0.Clear;  //clear any data that was on the graph
  VSens1.Clear;
end;


(******************************************************************************
 btnTubingFrClick: This procedure launches the tubing frequency response test,
                   configuring the hardware and waveform generator as necessary.
                   Also, the memTest memo is enabled and loaded with instructions
                   for conducting this test.
 ******************************************************************************)
procedure TfrmMain.btnFRClick(Sender: TObject);
type TGraphPoint = Record
  X: Integer;
  Y: Double;
end;
var
  i, lowest, lastLow: Integer;
  pt1, pt2, pt3: TGraphPoint;
  Tp, A1, A2, DCoff, Fn: Double;    //calculated amplitude 1, 2, and pulse freq per AAMI TIR9, 9.2.4
  Error: string;
begin
  //refresh the visible xpoints currently on the graph
  //lblXpoints.Caption := 'Xpts shown: '+ IntToStr(xPtLower) + ' - ' +
  //                      IntToStr(xPtUpper);
  pt1.Y := VSens1.YValue[xPtLower];    //init to minimum x point currently in view.
  pt1.X := xPtLower;
  //Find lowest point curretly in view.
  for i := xPtLower to xPtUpper do
    begin
      if VSens1.YValue[i] < pt1.Y then
        begin
          pt1.Y := VSens1.YValue[i];
          pt1.X := i;
        end;
    end;
  //Now find opposite peak of this biggest negative wave
  pt2.Y := pt1.Y;
  for i := pt1.X to xPtUpper do
    if VSens1.YValue[i] > pt2.Y then
        begin
          pt2.Y := VSens1.YValue[i];
          pt2.X := i;
        end;
  //Finally, find the immediate next low peak
  pt3.Y := pt2.Y;
  for i := pt2.X to xPtUpper do
    if VSens1.YValue[i] < pt3.Y then
       begin
          pt3.Y := VSens1.YValue[i];
          pt3.X := i;
        end;
  //Now mark points on tchart.
  sPt1.X := VSens1.CalcXPos(pt1.X);
  sPt1.Y := VSens1.CalcYPosValue(pt1.Y);
  DrawCrossHairs(clBlack, sPt1);
  sPt2.X := VSens1.CalcXPos(pt2.X);
  sPt2.Y := VSens1.CalcYPosValue(pt2.Y);
  DrawCrossHairs(clBlack, sPt2);
  sPt3.X := VSens1.CalcXPos(pt3.X);
  sPt3.Y := VSens1.CalcYPosValue(pt3.Y);
  DrawCrossHairs(clBlack, sPt3);
  //Now show the amplitude lines
  DrawAmpLines(clBlack);
  //Now calculate and display damping coefficient and natural frequency
  A1 := pt2.Y - pt1.Y;
  A2 := pt2.Y - pt3.Y;
  Tp := (pt3.X- pt1.X) * 1/LJ.AIO[0].NumScans; //assumes a rate of 2K Samples/Second
  DCoff := -1*(Ln(A1/A2)) / Power((Power(Pi,2)+Power(Ln(A1/A2),2)),0.5);
  Fn := (1/Tp)/Power(1-Power(DCoff,2),0.5);
  ShowMessage('A1: '+FloatToStr(A1) + #13 +
              'A2: '+FloatToStr(A2) + #13 +
              'Tp: '+FloatToStr(Tp) + #13 +
              'DCoff: '+FloatToStr(DCoff) + #13 +
              'Fn: ' + FloatToStr(Fn));
end;

(***************************************************************************
btnRunClick: Set the HT into continuous mode (start running pattern to PPG)
****************************************** *************************************)
procedure TfrmMain.btnRunClick(Sender: TObject);
var
  Error: string;
begin
  if not DDSSetSingleWave(HT.mHnd, False) then
         Error := 'Failed to disable single .';
  if Error <> '' then
    ShowMessage(Error)
      else
        PPGActive := True;
end;

(***************************************************************************
btnAinScrollStart: Starts capture of information in sample(non-stream) mode
*******************************************************************************)
procedure TfrmMain.btnAinScrollStartClick(Sender: TObject);
begin
  tcGraph1.LeftAxis.Automatic := False;
  tcGraph1.LeftAxis.Maximum := 180.0;
  tcGraph1.LeftAxis.Minimum := -10.0;
  tcGraph1.BottomAxis.Automatic := False;
  tcGraph1.BottomAxis.Maximum := 1000;
  tcGraph1.MaxPointsPerPage := 1000;
  tcGraph1.BottomAxis.Minimum := 1;
  if LJ.doStream then
    btnStopScanClick(Self);
  pnlScan.Enabled := False;
  xPos := 1;
  tmrScroll.Enabled := True;
end;

(***************************************************************************
btnAinScrollStop: Stops capture of information in sample(non-stream) mode
*******************************************************************************)
procedure TfrmMain.btnAinScrollStopClick(Sender: TObject);
begin
  tmrScroll.Enabled := False;
  pnlScan.Enabled := True;
end;

(******************************************************************************
btnCheckCalClick: Creates slow stair step voltage waveform to allow measurements
                  to be made with calibrated DVM, verifying measurement integrity
                  of ADC in LabJack.
*******************************************************************************)
procedure TfrmMain.btnCheckCalClick(Sender: TObject);
var
  i, j: Integer;
  Val: Double;
begin
  //config AIN 2 and 3 gain
  LJ.SetAnalogGain(2, LJ_rgBIP10V);
  LJ.SetAnalogGain(3, LJ_rgBIP10V);
  //first, set the chart vertical axis (voltage) for min and max of +/- 10V
  tcGraph1.LeftAxis.Maximum := 10.0;
  tcGraph1.LeftAxis.Minimum := -10.0;
  CalMode := True;
  tmrScroll.Enabled := True;
  VSens1.Clear;
  VSens0.Clear;
  CalXA := -10;
  CalXALast := CalXA;
  CalXB := 10;
  CalY := 1;
 { //Now do stair-step voltages to the A/D, and plot the values.
 for i := -10 to 10 do
    begin
     LJ.SetLJTickVal(0,i);
     LJ.SetLJTickVal(1,i);
      for j := 1 to 100 do
        begin
          LJ.GetAnalogInput(2, Val);
          VSens0.AddY(Val);
          //VSens0.RefreshSeries;
          LJ.GetAnalogInput(3, Val);
          VSens1.AddY(Val);
          //VSens1.RefreshSeries;
          //sleep(10);
       end;
    end;  }
end;

(******************************************************************************
LoadSelectWaveForm(): Allows user to select an existing output waveform (.opf)
                  for the PPG.
*******************************************************************************)
procedure TfrmMain.LoadWaveForm(fN: string);
var
  HTOffset, tmp: Word;  //requried to adjust for inversed outpout of HT1025 (0=+3.5V, 2048 = 0V)
  wave: array of Word;
  i: Integer;
  f: file of Word;
  numRead:Integer;
  Error: string;
  rate: Double;
  pCnt, pPer: pInteger;
begin
  try
    try
      VSens0.Clear;    //prevents garbage in the chart
      VSens1.Clear;
      imgWaveForm.Picture.Bitmap.LoadFromFile(fN);
      lblWaveForm.Caption := ChangeFileExt(ExtractFileName(fN), '');
      AssignFile(f, ChangeFileExt(fN, '.opf'));
      Reset(f);
      SetLength(HT.mWaveForm, FileSize(f));
      i:=Low(HT.mWaveForm);
      while not EoF(F) do
        begin
          read(f, HT.mWaveForm[i]);
          inc(i);
        end;
    finally
      CloseFile(f);
    end;
  except
    ShowMessage('Failure encountered while processing file.');
  end;
  (*******  Now adjust entire waveform (if necessary) for zero reference.  *******)
  HT.mWaveMin := HT.mWaveForm[0];    //initialize to the 1st value in the array
  HT.mWaveMax := HT.mWaveForm[0];
  for i := Low(HT.mWaveForm) to High(HT.mWaveForm) do  //Find Min/Max values in array
    begin
      if HT.mWaveForm[i] < HT.mWaveMin then
        HT.mWaveMin := HT.mWaveForm[i];
      if HT.mWaveForm[i] > HT.mWaveMax then
        HT.mWaveMax := HT.mWaveForm[i];
    end;
  if HT.mWaveMin > 0 then
    begin
       for i := Low(HT.mWaveForm) to High(HT.mWaveForm) do   //adjust entire waveform down to zero reference
         begin
           HT.mWaveForm[i] := HT.mWaveForm[i]-HT.mWaveMin;
         end;
       HT.mWaveMax := HT.mWaveMax - HT.mWaveMin;   //Value was just adjusted down, so reset to new val.
       HT.mWaveMin := 0;                           //value was just adjusted down to zero.
    end;
 (**** Now make inverted copy of waveform to deal with the HT1025 inverse relationship between cnt and output ******)
  SetLength(wave, Length(HT.mWaveForm));
  for i := Low(HT.mWaveForm) to High(HT.mWaveForm) do
    begin
      HTOffset := 2048-HT.mWaveForm[i];
      if HTOffset > 2048 then wave[i] := 2048   //can't be > 2048.
      else
        if HTOffset >= 0 then
        wave[i] := HTOffset     //account for inverse HT1025 output
          else
            wave[i] := 0;   //max +V allowed equates to HT1025 count of 0.
     end;
  tcGraph1.BottomAxis.Automatic := True;   //must enable bottom axis to auto postion.
  tcGraph1.LeftAxis.Automatic := True;
  (******  Now load waveform to HT1025G. ********)
  Error := '';
  HT.mNumPer := 1;
  HT.mFreqPts := Length(HT.mWaveForm);       //load with value- may not be needed.   DO NOT USE THIS
                                            //Value when calling DDSDownLoad- DDSSetFrequency changes this value.
  HT.mWaveFrmPoints := Length(HT.mWaveForm);
  //ShowMessage('Length(HT.mWaveForm): '+ IntToStr(Length(HT.mWaveForm)));
  rate := ((sbRate.Position*5+30)/60);
  if not DDSSetSingleWave(HT.mHnd, True) then
         Error := 'Failed to Enable single shot.';
  if not DDSSetFrequency(HT.mHnd, rate, HT.mFreqPts, HT.mNumPer ) then
        Error := Error+ 'Failed to set frequency. ';
  if not DDSDownload(HT.mHnd, @HT.mWaveForm[0], HT.mFreqPts) then
        Error := Error+'Failed to download waveform.';
  sbOutputChange(Self);     //default to 70 Torr.
  sbRateChange(Self);       //default to 60 bpm.
  if Error <> '' then
    ShowMessage(Error);
  sbRate.Enabled := True;
  sbOutput.Enabled := True;
end;

(******************************************************************************
btnSelectWaveFormClick: Calls function to load wave form file, based on file
                        selected.
*******************************************************************************)
procedure TfrmMain.btnSelectWaveFormClick(Sender: TObject);
begin
  if OpenDialog1.Execute then
    LoadWaveForm(OpenDialog1.FileName);
end;
(******************************************************************************
StartScan: Prepare application for LJ stream mode, and start stream.
*******************************************************************************)
procedure TfrmMain.btnStartScanClick(Sender: TObject);
begin
  //configure the U6 communication status bars
  gaU6Backlog.MaxValue := 2000;
  gaDrvBacklog.MaxValue := 2000;
  gaU6Backlog.Progress := 0;
  gaDrvBacklog.Progress := 0;
  //initialize the ScanArrays for each channel
  SetLength(ScanArray0,0);
  SetLength(ScanArray1,0);
  VSens0.Clear;
  VSens1.Clear;
  //Disable other items on the GUI to prevent errors with the U6
  cbScanRate.Enabled := False;
  btnStopScan.Enabled := True;
  btnStartScan.Enabled := False;
  bbFirst.Enabled := False;
  bbForward.Enabled := False;
  bbRewind.Enabled := False;
  bbLast.Enabled := False;
  pnlScan.Color := $00C5D2B5;
  pnlFreeRun.Enabled := False;
  lblXPoints.Caption := 'X Points on Chart:';  //clear previous
  lblScanNum.Caption := 'Scan Page#: ';    //clear previous
  lblTotalScans.Caption := 'Total Scans:';  //clear previous
  pbScan.Position := pbScan.Min;
  tcGraph1.UndoZoom;   //undo any previously set zooms.
  LJ.ConfigStream;
end;
(***************************************************************************
btnStopClick: Sets the 1025 waveform generator to single wave, which effectively
              stops the waveform generation. This is the primary way of enabling
              and disabling continuous waveform generation.
*******************************************************************************)
procedure TfrmMain.btnStopClick(Sender: TObject);
var
  Error: string;
begin
  DDSSetSingleWave(HT.mHnd,True);
  if Error <> '' then
    ShowMessage(Error)
      else
        PPGActive := False;
end;
(***************************************************************************
StopScan: Stop streaming.
*******************************************************************************)
procedure TfrmMain.btnStopScanClick(Sender: TObject);
begin
  tmrStream.Enabled := False;
  LJ.doStream := False;
  LJ.StopStream;
  //Re-enable the GUI to allow review of scans
  cbScanRate.Enabled := True;
  btnStartScan.Enabled := True;
  btnStopScan.Enabled := False;
  bbFirst.Enabled := True;
  bbForward.Enabled := True;
  bbRewind.Enabled := True;
  bbLast.Enabled := True;
  pnlScan.Color := clSilver;
  pnlFreeRun.Enabled := True;
  if Length(ScanArray0)-1 > 0 then
    begin
      ScanIndex := Length(ScanArray0)-1;
    end;
end;



procedure TfrmMain.Button2Click(Sender: TObject);
begin
  tcGraph1.BottomAxis.Automatic := True;
end;



(***************************************************************************
cbGain: Configures LabJack DIO outputs 0 and 1 which set the gains to the
           instrumentation amplifier.
*******************************************************************************)
procedure TfrmMain.cbGainCh0Click(Sender: TObject);
begin
  case cbGainCh0.ItemIndex of
  0: begin
       LJ.SetAnalogGain(0, LJ_rgBIP10V);
       LJ.SetAnalogGain(1, LJ_rgBIP10V);
       tcGraph1.LeftAxis.Minimum := -5;
       tcGraph1.LeftAxis.Maximum := 5;
       LJ.IntAmpGain := 1;
     end;
  1: begin
       LJ.SetAnalogGain(0, LJ_rgBIP1V);
       LJ.SetAnalogGain(1, LJ_rgBIP1V);
       tcGraph1.LeftAxis.Minimum := -2;
       tcGraph1.LeftAxis.Maximum := 2;
       LJ.IntAmpGain := 10;
     end;
  2: begin
       LJ.SetAnalogGain(0, LJ_rgBIP1V);
       LJ.SetAnalogGain(1, LJ_rgBIP1V);
       tcGraph1.LeftAxis.Minimum := -1;
       tcGraph1.LeftAxis.Maximum := 1;
       LJ.IntAmpGain := 100;
     end;
  3: begin
       LJ.SetAnalogGain(0, LJ_rgBIPP01V);
       LJ.SetAnalogGain(1, LJ_rgBIPP01V);
       tcGraph1.LeftAxis.Minimum := -0.5;
       tcGraph1.LeftAxis.Maximum := 0.5;
       LJ.IntAmpGain := 1000;
     end;
  end;

  LJ.GetAnalogGain(0);
  //Memo1.Lines.Add(Format('%s %f', ['AIN0 Gain: ', LJ.AIO[0].AioRange]));
  cbGainCh0.Text := IntToStr(cbGainCh0.ItemIndex);
end;

(***************************************************************************
cbResolution: Configures LabJack resolution for channels 0 and 1; higher
              resolution levels slow the aquisition rate for little gain in
              bits of resolution.
*******************************************************************************)
procedure TfrmMain.cbResolutionCh0CloseUp(Sender: TObject);
begin
  LJ.SetResolution(0,cbResolutionCh0.ItemIndex);
  LJ.SetResolution(1,cbResolutionCh0.ItemIndex);
  //Memo1.Lines.Add('Resolution CH0 configured to '+cbResolutionCh0.Items[cbResolutionCh0.ItemIndex]);
end;

(***************************************************************************
cbGain: Configures LabJack scan rate, which refers to the number of samples
        collected per second. Note: Sample rate refers to a single channel,
        whereas scan rate refers to all channels in a given set.
*******************************************************************************)
procedure TfrmMain.cbScanRateCloseUp(Sender: TObject);
var
  i: Integer;
begin
  case cbScanRate.ItemIndex of
    0: LJ.scanRate := 100;
    1: LJ.scanRate := 500;
    2: LJ.scanRate := 1000;
    3: LJ.scanRate := 2000;
    4: LJ.scanRate := 5000;
    5: LJ.scanRate := 10000;
  end;
  //tcGraph1.BottomAxis.Maximum := Round(LJ.scanRate);
end;
(***************************************************************************
DrawAmpLines: Draws amplitude measurement lines at the points passed in
*******************************************************************************)
procedure TfrmMain.DrawAmpLines(color: TColor);
var
  tmpPt1, tmpPt2, tmpPt3: TPoint;
begin
  with tcGraph1.Canvas do
  begin
  //first draw PT1 reference Line
    Pen.Color := clBlack;
    Pen.Style := psDot;
    Pen.Width := 1;
    MoveTo(sPt1.X+12, sPt1.Y);
    LineTo(sPt2.X+12, sPt1.Y);
    //Now draw PT2 reference Line
    MoveTo(sPt2.X+12, sPt2.Y);
    LineTo(sPt3.X+12, sPt2.Y);
    //Now draw amplitude line1
    MoveTo(sPt2.X,sPt1.Y+5);
    LineTo(sPt2.X, sPt2.Y-5);
    //Draw amplitude line 2
    MoveTo(sPt3.X, sPt3.Y+5);
    LineTo(sPt3.X, sPt2.Y-5);
    //Draw amplitude value 1
    Font.Size := 14;
    Font.Style := [fsBold];
    TextOut(sPt2.X-20, (sPt1.Y-sPt2.Y)div 2 + sPt2.Y,'A1', FALSE);
    //Draw amplitude value 2
    TextOut(sPt3.X-20, (sPt3.Y-sPt2.Y)div 2 + sPt2.Y,'A2', FALSE);
    //Draw Tp reference line 1
    Pen.Color := clGreen;
    Font.Color := clGreen;
    MoveTo(sPt1.X, sPt1.Y+5);
    LineTo(sPt1.X, spt1.Y+20);
    //Draw Tp reference Line 2
    MoveTo(sPt3.X, sPt1.Y+5);
    LineTo(sPt3.X, spt3.Y+30);
    //Draw Tp line
    MoveTo(sPt1.X+5, spt1.Y+10);
    LineTo(sPt3.X-5, spt1.Y+10);
    //Draw Tp text
    TextOut((sPt3.X - sPt1.X)div 2 + sPt1.X,sPt1.Y+10, 'Tp', FALSE);
  end;
end;
(***************************************************************************
DrawCrossHairs: Draws cross hairs of the specified colors at the point passed in
*******************************************************************************)
procedure TfrmMain.DrawCrossHairs(color: TColor; pt: TPoint);
begin
  tcGraph1.Canvas.Pen.Color := color;
  tcGraph1.Canvas.Pen.Width := 3;
  tcGraph1.Canvas.Pen.Style := psSolid;
  tcGraph1.Canvas.MoveTo(pt);
  pt.X := (pt.X + 10);
  tcGraph1.Canvas.LineTo(pt);
  pt.X := (pt.X -20);
  tcGraph1.Canvas.LineTo(pt);
  pt.X := (pt.X + 10);
  tcGraph1.Canvas.LineTo(pt);
  pt.Y := (pt.Y + 10);
  tcGraph1.Canvas.LineTo(pt);
  pt.Y := (pt.Y -20);
  tcGraph1.Canvas.LineTo(pt);
  pt.Y := (pt.Y + 10);
  tcGraph1.Canvas.LineTo(pt);
end;

procedure TfrmMain.edExcitationChange(Sender: TObject);
begin
  LJ.torrMultiplier := LJ.CalcTorrMult;    //init the Torr multiplier.
end;

(***************************************************************************
ReviewScans: Proceeds through saved scans (fwd or backwards) based on button
             pressed.
*******************************************************************************)
procedure TfrmMain.ReviewScan(Sender: TObject);
begin
  tcGraph1.BottomAxis.Automatic := True;   //must enable bottom axis to auto postion.
  if Sender =bbFirst then
    begin
      tcGraph1.Page := 0;  //go to first page
    end else
  if Sender =bbRewind then
    begin
      if tcGraph1.Page > 0 then  //go to Previous page
        tcGraph1.PreviousPage;
    end else
  if Sender =bbForward then
    begin
      if tcGraph1.Page < tcGraph1.NumPages then  //go to next page
        tcGraph1.NextPage;
    end else
   if Sender =bbLast then
      tcGraph1.Page := tcGraph1.NumPages;   //go to last page.
   lblScanNum.Caption := 'Scan #: '+IntToStr(tcGraph1.Page);
   pbScan.Max := tcGraph1.NumPages;
   pbScan.Position := tcGraph1.NumPages;
   tcGraph1.Refresh;
end;

(***************************************************************************
cbChxClick: Enables/disables individual channels on the U6.
*******************************************************************************)
procedure TfrmMain.cbCh0Click(Sender: TObject);
var i: Integer;
begin
  if cbCh0.Checked then
    LJ.AIO[0].chStreamEnabled := True
      else
        LJ.AIO[0].chStreamEnabled := False;
//Determine number of channels that need to be configured
  with LJ do
    begin
      numScanCh := 0;   //initialize
      for i := Low(AIO) to High(AIO) do
      if AIO[i].chStreamEnabled = True then
      inc(numScanCh);
    end;
end;

procedure TfrmMain.cbCh1Click(Sender: TObject);
var i: Integer;
begin
  if cbCh1.Checked then
    LJ.AIO[1].chStreamEnabled := True
      else
        LJ.AIO[1].chStreamEnabled := False;
 with LJ do
    begin
      numScanCh := 0;   //initialize
      for i := Low(AIO) to High(AIO) do
      if LJ.AIO[i].chStreamEnabled = True then
      inc(numScanCh);
    end;
end;

(***************************************************************************
FormClose: Executes just prior to the form closing. Use to disconnect from
            resources, free memory etc.
*******************************************************************************)
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  //
end;
(***************************************************************************
File1Click: Don't enable "Save Current Graph" menu item if currently updating
            graph.
*******************************************************************************)
procedure TfrmMain.File1Click(Sender: TObject);
begin
  if(LJ.doStream) or (tmrScroll.Enabled) then
    File1.Items[1].Enabled := False
    else
      File1.Items[1].Enabled := True;
end;
(***************************************************************************
FormCreate: Executes when the form is first created. Use to connect to
            resources, instantiate objects etc.
*******************************************************************************)
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  PrepareFastChart;
  InitParams;
  sbRate.Enabled := False;
  sbOutput.Enabled := False;
  SetLength(ScanArray0, 0);   //init to 0
  SetLength(ScanArray1, 0);   //init to 0
  lblXpoints.Caption := 'X Pts on Chart: '+ IntToStr(Round(tcGraph1.BottomAxis.Maximum));
end;



(***************************************************************************
GetInitialValues: Read the curent configuration of the LabJack and update the
                  controls on the form accordingly.
*******************************************************************************)
procedure TfrmMain.GetInitialValues;
begin
  LJ.GetAnalogGain(0);
  //Memo1.Lines.Add(Format('%s %.0f', ['AIN0 Gain: ', LJ.AIO[0].AioRange]));
  LJ.GetAnalogGain(1);
  //Memo1.Lines.Add(Format('%s %.0f', ['AIN1 Gain: ', LJ.AIO[1].AioRange]));
end;

(******************************************************************************
InitParams: Items to execute when the application is first started.
*******************************************************************************)
procedure TfrmMain.InitParams;
var
  Error: string;
begin
  //Find and configure the LabJack
  Error := '';
  LJConnectionExecute(frmMain);   //try to connect to labjack.
  if LJ.lngHandle <> 0 then    //LabJack was found and handle provided
    begin
       LJ.scanRate := 2000;  //init to 2000 scans/channel/sec
       tcGraph1.MaxPointsPerPage := 2000;
       cbScanRate.ItemIndex := 2;  //make sure box is set right.
       //Configure U6 Gain
       cbGainCh0.ItemIndex := 0;   //init to range of 10 V (no amplification)
       tcGraph1.LeftAxis.Minimum := -5;
       tcGraph1.LeftAxis.Maximum := 5;
       LJ.SetAnalogGain(1, LJ_rgBIP10V);
       LJ.SetAnalogGain(0, LJ_rgBIP10V);
       LJ.IntAmpGain := 1;     //default to 1x.
       //Configure External Instrumentation Amp Gain to 1
       LJ.PutBit(0, 1);  //set gain to x1000
       LJ.PutBit(1, 1);
       rgAmpGain.ItemIndex := 3;
       LJ.instrumentAmpGain := 1000;    // init to x1000.
       LJ.torrMultiplier := LJ.CalcTorrMult;    //init the Torr multiplier.
       //Default to both channels being enable
       LJ.AIO[0].chStreamEnabled := True;
       LJ.AIO[1].chStreamEnabled := True;
       LJ.numScanCh := 2;
  end
    else
      begin
        ShowMessage('LabJack not connected. ' +
                    'Must connect Lab Jack to proceed.');
        PageControl1.Enabled := False;
      end;
 //Now find and configure the HT1025.
  try
    HT := THT1025.Create;
    HT.mWaveFrmPoints := HT_MEM_PTS;    //default to 2048
    HT.mNumPer := 1;              //default to 1 period per waveform.
    HT.mFind;
    if HT.mFound then
      begin
        sbStatus.Panels[1].Text := 'HT1025 Status: Connected CH'+IntToStr(HT.mHnd)
      end
      else
        sbStatus.Panels[1].Text := 'HT1025 Status: Not Connected';
    if HT.mFound then
      begin
        if not DDSSetPowerOnOutput(HT.mHnd, False) then
          Error := error + 'Unable to set HT ouput on start to off.';
        if not DDSSetTrigger(HT.mHnd, False, False) then
          Error := 'Failed to set trigger source. ';
        if not DDSSetSingleWave(HT.mHnd, False) then
          Error := Error + ' Failed to set to continuous output. ';
        if Error <> '' then
          ShowMessage(Error);
      end;
  except
    ShowMessage('Error encountered while trying to communicate with HT1025. ');
  end;

end;

(******************************************************************************
LJConnection: Searches for LabJack, and displays info (if found) on status bar.
*******************************************************************************)
procedure TfrmMain.LJConnectionExecute(Sender: TObject);
begin
  LJ := TLabJack.Create;
  LJ.errNum :=0;
  LJ.InitLabJack;              //try to open the labjack, and get params.
  if LJ.lngHandle <> 0 then    //LabJack was found and handle provided
  begin
    sbStatus.Panels[0].Text := Format('%s %s %.0f %s %.0f %s %.0f',
                                      ['LabJack V6 Status: Connected.   ',
                                       'Local Address: ',
                                       LJ.localID,
                                       '    Hardware Version: ',
                                       LJ.hwVersion,
                                       '    S/N: ',
                                       LJ.serialNum]);
     //tbBtnConnect.ImageIndex := 2;   //change to 'disconnect' image.
     GetInitialValues();
     LJ.scanRate := 1000;   //initialize
   end
   else
      begin
        ShowMessage('LabJack not found.'+#13+
                   'Ensure LabJack is connected to USB port.');
        //tbBtnConnect.ImageIndex := 0;   //change to 'connect' image.
      end;
end;

(******************************************************************************
PageControl1Change: Configures proper display of current page being displayed
*******************************************************************************)
procedure TfrmMain.PageControl1Change(Sender: TObject);
begin
 //
end;
procedure TfrmMain.pnlFreeRunClick(Sender: TObject);
begin

end;

(******************************************************************************
PrepareFastChart: Configures the TChart for maximum charting speed
*******************************************************************************)
procedure TfrmMain.PrepareFastChart;
// Prepare chart for maximum speed:
begin
  with tcGraph1 do
  begin
    ClipPoints := False;
    Title.Visible := False;
    Legend.Visible := False;
    LeftAxis.Axis.Width:=1;
    BottomAxis.Axis.Width:=1;
    BottomAxis.RoundFirstLabel := False;
    View3D := False;
    Canvas.ReferenceCanvas.Pen.OwnerCriticalSection := nil;
    Axes.FastCalc := True;
    MaxPointsPerPage := 1000;   //init to 1000
  end;
  VSens0.LinePen.OwnerCriticalSection := nil;
  VSens1.LinePen.OwnerCriticalSection := nil;
  VSens0.FastPen := True;
  VSens1.FastPen := True;

end;

(******************************************************************************
rgAmpGain: Configures LabJack DIO outputs 0 and 1 which set the gains to the
           instrumentation amplifier.
*******************************************************************************)
procedure TfrmMain.rgAmpGainClick(Sender: TObject);
begin
  case rgAmpGain.ItemIndex of
    0: begin
         LJ.PutBit(0, 0);  //set gain to x1
         LJ.PutBit(1, 0);
         LJ.instrumentAmpGain := 1;
       end;
    1: begin
         LJ.PutBit(0, 0);  //set gain to x10
         LJ.PutBit(1, 1);
         LJ.instrumentAmpGain := 10;
       end;
    2: begin
         LJ.PutBit(0, 1);  //set gain to x100
         LJ.PutBit(1, 0);
         LJ.instrumentAmpGain := 100;
       end;
    3: begin
         LJ.PutBit(0, 1);  //set gain to x1000
         LJ.PutBit(1, 1);
         LJ.instrumentAmpGain := 1000;
       end;
  end;
  LJ.torrMultiplier := LJ.CalcTorrMult;
end;
(***************************************************************************
SaveCurrentGraph: Saves the current graph to a .bmp file.
*******************************************************************************)
procedure TfrmMain.SaveCurrentGraphClick(Sender: TObject);
begin
  if SaveDialog1.Execute then
    try
      tcGraph1.SaveToBitmapFile(SaveDialog1.FileName);
    except
      ShowMessage('Uanble to save current graph to file.');
    end;
end;

(*****************************************************************************
 sbDacXChange sets DAC output according to slider value.
 ****************************************************************************)
procedure TfrmMain.sbDacAChange(Sender: TObject);
begin
  lblDacA.Caption := format('%.2f V',[sbDacA.Position*0.01]);
  LJ.SetLJTickVal(0, sbDacA.Position*0.01);
end;

procedure TfrmMain.sbDacBChange(Sender: TObject);
begin
  lblDacB.Caption := format('%.2f V',[sbDacB.Position*0.01]);
  LJ.SetLJTickVal(1, sbDacB.Position*0.01);
end;
(*****************************************************************************
 sbOutputChange sets Waveform pressure equivalent according to slider value.
 Uses formula:   Count = (Torr-140)/-0.068359
 ****************************************************************************)
procedure TfrmMain.sbOutputChange(Sender: TObject);
var
  i: Integer; desiredAmp, currentAmp: Word;
  scaler: Double; Error: string;
  wave: array of word;
begin
  lblVOut.Caption := format('%d Torr',[(sbOutPut.Position*5+TORROFFSET)]);  //update the label
  currentAmp := abs(HT.mWaveMax-Ht.mWaveMin);       //Get the wave p-p amplitude.
  desiredAmp := Round((sbOutPut.Position*5+TORROFFSET)/0.068359);
  scaler := desiredAmp/currentAmp;
  SetLength(wave, Length(HT.mWaveForm));
  for i := Low(HT.mWaveForm) to High(HT.mWaveForm) do
    wave[i] := 2048-(Round(HT.mWaveForm[i] * scaler));  //Scales the waveform
  Error := '';
  if not DDSDownLoad(HT.mHnd, @wave[0], HT.mWaveFrmPoints) then
    Error := Error + 'Failed to load pattern. ';
  if Error <> '' then
    ShowMessage(Error)
end;

(*****************************************************************************
 sbPtsPerPage: sets the number of points displayed on the graph per page,
               configures the lab jack sample rate to match.
 ****************************************************************************)
procedure TfrmMain.sbPtsPerPageChange(Sender: TObject);
begin
  lblPtsPerPage.Caption := IntToStr(sbPtsPerPage.Position);
  tcGraph1.MaxPointsPerPage := sbPtsPerPage.Position;
  LJ.scanRate := sbPtsPerPage.Position;

end;

(*****************************************************************************
 sbRateChange: sets Waveform Output freq according to slider value.
 ****************************************************************************)
procedure TfrmMain.sbRateChange(Sender: TObject);
var
  rate: Double; Error: string;
begin
  rate := ((sbRate.Position*5+30)/60);
  lblBPS.Caption := IntToStr(sbRate.Position*5+30) + ' BPM';
  if not DDSSetFrequency(HT.mHnd, rate, HT.mFreqPts, HT.mNumPer ) then
        Error := Error + 'Failed to set frequency. ';
  if Error <> '' then
    ShowMessage(Error)
end;
(*****************************************************************************
 ShowExcitationEdit: Displays the excitation voltage edit box if checked,
                     allowing user to enter a custome excitation voltage. NOTE:
                     The defalult voltage is 5V, provided by the LabJack.
 ****************************************************************************)
procedure TfrmMain.ShowExcitationEditClick(Sender: TObject);
begin
  if ShowExcitationEdit.Checked then
    begin
      ShowExcitationEdit.Checked := False;
      Label14.Visible := False;
      edExcitation.Visible := False;
    end
    else
      begin
        ShowExcitationEdit.Checked := True;
        Label14.Visible := True;
        edExcitation.Visible := True;
      end;
end;
(*****************************************************************************
 ShowPredefined Tests: Displays the predefined tests panel if checked to allow
                       user to select and run predefined tests.
 ****************************************************************************)
procedure TfrmMain.ShowPredefinedTestsClick(Sender: TObject);
begin
  if ShowPredefinedTests.Checked then
    begin
      PnlTests.Visible := True;
      Label21.Visible := True;
    end
    else
      begin
      PnlTests.Visible := False;
      Label21.Visible := False;
    end

end;

(*****************************************************************************
 tbFreeRunChange: Changes the request rate for the scroll mode timer.
 ****************************************************************************)
 procedure TfrmMain.tbFreeRunChange(Sender: TObject);
begin
  tmrScroll.Enabled := False;
  tmrScroll.DueTime := 5;
  tmrScroll.Period := tbFreeRun.Position*5;
  lblFreeRunPeriod.Caption := IntToStr(tbFreeRun.Position*5) + ' ms';
  tmrScroll.Enabled := True;
end;

(*****************************************************************************
 tcGraph1.AfterDraw: Fires each time after the graph is redrawn.
 ****************************************************************************)
procedure TfrmMain.tcGraph1AfterDraw(Sender: TObject);
begin
  //xPtLower := Round(tcGraph1.BottomAxis.Maximum)-
  //                      tcGraph1.MaxPointsPerPage;
  //xPtupper := (Round(tcGraph1.BottomAxis.Maximum));
  xPtLower := round(tcGraph1.BottomAxis.Minimum);
  xPtUpper := Round(tcGraph1.BottomAxis.Maximum);
  lblXpoints.Caption := 'Xpts shown: '+ IntToStr(xPtLower) + ' - ' +
                        IntToStr(xPtUpper);

end;
(*****************************************************************************
 tcGraph1Zoom: Fires each time after the graph is zoomed
 ****************************************************************************)
procedure TfrmMain.tcGraph1Zoom(Sender: TObject);
begin
  //
end;

(*****************************************************************************
 tmrScroll runs calls to sequentially read and display data stored in by the UD driver.
 ****************************************************************************)
procedure TfrmMain.tmrScrollTimer(Sender: TObject);
var
  Val: VALUE;
begin
  if CalMode then
    begin
      //Now do stair-step voltages to the A/D, and plot the values.
      if CalXA < 10 then
        begin
          if CalXALast <> CalXA then
            begin
              LJ.SetLJTickVal(0,CalXA);
              LJ.SetLJTickVal(1,CalXB);
              CalXALast := CalXA;
            end;
          if CalY <= 49 then
            begin
              LJ.GetAnalogInput(2, Val);
              pnlVDacA.Caption := format('%.2f V',[val]);
              VSens0.AddY(Val);
              LJ.GetAnalogInput(3, Val);
              pnlVDacB.Caption := format('%.2f V',[val]);
              VSens1.AddY(Val);
              inc(CalY);
           end
           else
             begin
               CalY := 0;
               inc(CalXA);
               dec(CalXB);
             end;
        end
        else
          begin
            tmrScroll.Enabled := False;
            CalMode := False;
          end;
    end
//Not In Cal Mode
    else
      begin
        if cbCH0.Checked then
          begin
            LJ.GetAnalogInput(0, Val);
            VSens0.AddXY(xPos,Val*LJ.torrMultiplier);
          end;
        if cbCH1.Checked then
           begin
             LJ.GetAnalogInput(1, Val);
             VSens1.AddXY(xPos,Val*LJ.torrMultiplier);
           end;
        //Label6.Caption := IntToStr(xPos);
        if xPos >= tcGraph1.MaxPointsPerPage then
          begin
            tmrScroll.Enabled := False;
            VSens1.Clear;
            VSens0.Clear;
            xPos := 1 ;
            tmrScroll.Enabled := True;
          end
           else
            inc(xPos);
      end;
end;

(*****************************************************************************
 tmrStream runs calls to stream data stored by the UD driver.
 ****************************************************************************)
procedure TfrmMain.tmrStreamTimer(Sender: TObject);
begin
  LJ.ProcessStream;
  UpdateScanGraphActive;
  gaU6Backlog.Progress := round(LJ.U6Backlog);
  gaDrvBacklog.Progress := round(LJ.drvBacklog);
  sbStatus.Panels[2].Text := 'CH0: '+ format('%.0f Samples',[LJ.AIO[0].NumScans]);
  sbStatus.Panels[3].Text:= 'CH1: '+ format('%.0f Samples',[LJ.AIO[1].NumScans]);
end;

(**************************************************************************
UpdateScanGraphActive: Updates the graph with current information from a live
                       stream.
*******************************************************************************)
procedure TfrmMain.UpdateScanGraphActive;
var
  i, j: Integer;
begin
  if LJ.AIO[0].chStreamEnabled then
    begin
      j := VSens0.Count;
       for i := 1 to round(LJ.AIO[0].NumScans)-1 do
        begin
          VSens0.AddXY(j, LJ.AIO[0].streamData[i]*LJ.torrMultiplier);
          inc(j);
        end;
    end;
  if LJ.AIO[1].chStreamEnabled then
    begin
      j := VSens1.Count;
       for i := 1 to round(LJ.AIO[1].NumScans)-1 do
        begin
          VSens1.AddXY(j, LJ.AIO[1].streamData[i]*LJ.torrMultiplier);
          inc(j);
        end;

    end;
  lblTotalScans.Caption := 'Total Scans:'+ IntToStr(tcGraph1.NumPages);
  lblScanNum.Caption := 'Scan #: '+IntToStr(tcGraph1.Page);
  pbScan.Max := tcGraph1.NumPages;
  pbScan.Position := tcGraph1.NumPages;
  tcGraph1.Page := tcGraph1.NumPages-1;
end;


(***************************************************************************
UpdateScanGraphFromList: Updates the graph with saved information from a selection
                       of previous scans.
*******************************************************************************)
procedure TfrmMain.UpdateScanGraphFromList(val: Integer);
var
  i: Integer;
begin
 VSens0.Clear;
 VSens1.Clear;
 if LJ.AIO[0].chStreamEnabled then
    begin
      for i := Low(ScanArray0[val]) to High(ScanArray0[val]) do
        begin
          VSens0.AddXY(i, ScanArray0[val,i]);
          lblScanNum.Caption := IntToStr(val);
          pbScan.Position := val;
        end;
    end;
  if LJ.AIO[1].chStreamEnabled then
    begin
      for i := Low(ScanArray1[val]) to High(ScanArray1[val]) do
        VSens1.AddXY(i, ScanArray1[val,i]);
      if LJ.AIO[0].chStreamEnabled = False then
        begin
          lblScanNum.Caption := 'Scan #: '+IntToStr(val);
          pbScan.Position := val;
        end;
    end;
  sbStatus.Panels[2].Text := 'CH0: '+ IntToStr(High(ScanArray0[val]))+' Samples';
  sbStatus.Panels[3].Text := 'CH1: '+ IntToStr(High(ScanArray1[val]))+ 'Samples';
end;

end.
